I'm Terrence

ffmpeg 实操

0x1背景

前段时间去三亚深潜了,因为考证回来后第一次的 FD, 为了记录下所见的海底世界,临出发前在淘宝租了台 go pro 7。在整个使用过程中,除了2次无故的死机外,拍得还是挺顺心的。然后回来得把sd卡的视频转移到自己的电脑,这才发现,几天下来的视频已达到 80多g (一个 8 min 的视频就8g了),md,我的mac book 才多少g啊,哪吃得下!
因此把这些视频压缩这个任务优先级已经很高了,在这个契机下,我去复习了下视频编解码相关的知识。

0x2 视频压缩(编码)

所谓视频,其实就是很多张图片,在一定时间内轮播完。比如,如果视频的帧率是24帧的,就是1秒内轮播完24张图片。go pro 的帧率在默认模式去到了60帧了,还能不大吗··
然后这里的压缩,就可以分为两种了,一种是帧内压缩,即图片压缩,另一种是帧间压缩,简要说来就是对于前后两帧,编码后面那帧时,只把基于前面那一帧不同的地方给编码,这样对于前后两帧差别不大的地方,压缩率是很高的。

0x2-1 ffmpeg

ffmpeg 是一套功能强大的,开源的,音视频处理工具,功能包括但不限于包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。

0x2-2 码率, 帧率,分辨率

帧率 (FPS):1s 中传输图片的的数量,主要影响视频的流畅度,帧率越高,流畅度越好,帧率越低,画面就越有跳跃感。视频一般的帧率有24就很流畅了,而 go pro 采集的时候有两种模式去到帧率去到 60 才有稳定的功能,导出的视频能不大才怪

分辨率:图片的尺寸,分辨率越大,图片的尺寸就越大了

码率:每 s 图片压缩后的数据量,而码率 x 时长就是视频的体积了。分辨率 x 帧率 = 每 s 在压缩前的数据量。

在码率一定的情况下,分辨率与图像清晰度成反比关系,分辨率越大,图像就越不清晰。

在分辨率一定的情况下,码率与图像清晰度成正比关系,码率越大,图像就越清晰。

0x2-3 压缩效果

可以尝试在帧率,分辨率,码率三个维度作下牺牲,来改变视频的体积。

-r 改 帧率
ffmpeg -i input -r 24

-s 改 分辨率
ffmpeg -i input -s 640x480

-b:v 改 码率

ffmpeg -i input -b:v 64k

直接修改其中一个或多个参数,都可以来打到压缩体积的效果,但画质惨不忍睹啊,由于硬盘容量实在有限,原视频在写这篇日志之前已经删光了,无法上图对比··

0x2-4 h265

ffmpeg -i input 这句可以看到原来的视频已经是用了 h264 编码的了,业界其实还有一种更先进的 h265 编码,立刻去尝试下!

安装 ffmpeg 这一块就不在这里详述了,这里主要在用 ffmpeg 进行 h265 编码遇到过的坑。

H265 需要借助 libx265 的库,而通过 brew 的方式来安装 ffmpeg 的话,由于 homebrew 本身是不支持带有 options 的方式安装 ffmpeg , 所以这种方式无法一步到位地把 ffmpeg 跟 libx265 关联起来,这个时候,需要借助 homebrew-ffmpeg 这个第三方库来给 ffmpeg 添加options 及 特性,而 这个库默认把 libx265 的能力添加进来了。

按步骤执行以下几句:

1
2
3
4
5
6
7
8
brew install ffmpeg

brew tap homebrew-ffmpeg/ffmpeg

brew install homebrew-ffmpeg/ffmpeg/ffmpeg

//关联 ffmpeg
brew link ffmpeg

至此, 命令行的 ffmpeg 就有了 libx265 的能力了

1
ffmpeg -i /Users/mac/Desktop/mp4/GH010105.MP4 -c:v libx265  /Users/mac/Documents/newFiles/GH010105_h265_1.mp4

原体积 8g 的 时长 8min的 MP4文件,经 h265 转换后,体积为 300M+,视频展示效果从肉眼上已经很难分辨出有什么不同,经过老婆的检验,清晰度是 ok 的。唯一有点不足的是,转换时间太长了,转换60+个文件,用下面这个批量脚本去跑,跑了一整天才全部完成··

批量转 h265

要转视频实在太多了,于是用 python 写了个批量转换 h265 的脚本

1
python /Users/mac/Documents/pythonTest/test.py /Users/mac/Desktop/mp4  /Users/mac/Desktop/mp4Output

指定输入文件夹,输出文件夹即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import os
import sys


def getFilesIn(path) :
g = os.walk(path)
sourceFileList = []
for path,dir_list,file_list in g:
for file_name in file_list:
sourceFileList.append(os.path.join(path, file_name))
return sourceFileList

# ffmpeg -i /Users/mac/Desktop/mp4/GH010105.MP4 -c:v libx265 /Users/mac/Documents/newFiles/GH010105_h265_1.mp4
def convertToH265(file):
print("############")
outputFilePath = outputPath +'/'+ os.path.basename(file)
print("输出文件:"+outputFilePath)
print(file)
print("=======")
print(os.system('ffmpeg -i ' + str(file) + " -c:v libx265 " + outputFilePath))

args = sys.argv
print (args[1])
inputPath = args[1]
outputPath = args[2]

print("输入路径:"+inputPath)
print("输出路径:"+outputPath)


for file in getFilesIn(inputPath):
print("开始转换: "+file+" basename: "+os.path.basename(file))
convertToH265(file)

视频合并

这个需求是来源于从go pro 导出的时长较长的mp4,都被分拆出好几个duration 是有 8 min 的子文件了,在解决完压缩问题后,我们在看看要怎么把分拆过的视频合并起来。

方法1

ffmpeg -i "concat:FD1-1.MP4|FD1-2.MP4|" -c copy FD1-con.mp4

但是失败,结果还是只有FD1-1的内容,见有warning
Found duplicated MOOV Atom. Skipped it

输出只含有第一个文件的信息,我们再用方法2试下

方法2

  1. 创建一个文件 concatFile.txt,把需要合并的文件名称写进去:
1
2
3
4
5
file 'FD2-1.MP4'
file 'FD2-2.MP4'
file 'FD2-3.MP4'
file 'FD2-4.MP4'
file 'FD2-5.MP4'
  1. 执行

    ffmpeg -f concat -i concatFile.txt -c copy fd2output.mp4

查看输出结果 fd2output.mp4, 已成功合并了。

总结

学习技术, 无非是为了解决实际问题,技术再好,境界越高,若未能落地应用,也是空中楼阁而已。这次能通过编码的方式来解决来源于生活的问题,有作为技术人的一种自豪感啊。